package com.xiaoshuidi.cloud.framework.pay.core.client.impl.yinlian;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.net.URLDecoder;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.xiaoshuidi.cloud.framework.common.core.KeyValue;
import com.xiaoshuidi.cloud.framework.common.exception.ErrorCode;
import com.xiaoshuidi.cloud.framework.common.exception.ServiceException;
import com.xiaoshuidi.cloud.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils;
import com.xiaoshuidi.cloud.framework.pay.core.client.AbstractPayCodeMapping;
import com.xiaoshuidi.cloud.framework.pay.core.client.PayCommonResult;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayNotifyDataDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.AbstractPayClient;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberBindReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberBindRespDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberCreateReqDTO;
import com.xiaoshuidi.cloud.framework.pay.core.client.impl.tongshangyun.dto.MemberCreateRespDTO;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.xiaoshuidi.cloud.framework.common.util.json.JsonUtils.toJsonString;

@Slf4j
public abstract class AbstractYinlianPayClient extends AbstractPayClient<YinlianClientConfig> {
    protected OkHttpClient okHttpClient;

    @Override
    protected void doInit() {
        okHttpClient = new OkHttpClient.Builder()
                .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
                .connectTimeout(Duration.ofMinutes(1))
                .readTimeout(Duration.ofMinutes(1))
                .writeTimeout(Duration.ofMinutes(1))
                .hostnameVerifier((hostname, session) -> true)
                .build();
    }

    public AbstractYinlianPayClient(Long channelId, String channelCode,
                                    YinlianClientConfig config, AbstractPayCodeMapping codeMapping) {
        super(channelId, channelCode, config, codeMapping);
    }

    protected ObjectNode baseParams(String mid, String tid) {
        ObjectNode json = JsonUtils.getObjectMapper().createObjectNode();
        json.put("requestTimestamp", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));    // 报文请求时间
        json.put("mid", mid);
        json.put("tid", tid);
        return json;
    }

    protected String nextOrderNo() {
        return config.getPrefix() + DateUtil.format(new Date(), "yyyyMMddHHmmssSSS") + RandomStringUtils.randomNumeric(7);
    }

    private final DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Override
    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
        //buyerUsername=136***@163.com&msgType=trade.notify&payTime=2023-06-29+14:36:00&buyerCashPayAmt=1&connectSys=UNIONPAY&sign=12C10C5C7ED25B3DFB764987DFE590002D1E77C0426BAB889C22236065122BAA&merName=合肥市住房租赁发展股份有限公司&mid=89834017013515M&invoiceAmount=1&settleDate=2023-06-29&billFunds=银行卡:1&buyerId=2088102119264750&mchntUuid=2d9081bd88beb8f00188d12467f325d5&tid=01803523&instMid=APPDEFAULT&receiptAmount=1&couponAmount=0&KA=Erbn&cardAttr=CREDIT_CARD&targetOrderId=2023062922001464751439141095&signType=SHA256&billFundsDesc=银行卡支付0.01元。&orderDesc=合肥市住房租赁发展股份有限公司&seqId=34913880623N&merOrderId=33PF202306291432268094563086&targetSys=Alipay+2.0&totalAmount=1&createTime=2023-06-29+14:32:27&buyerPayAmount=1&notifyId=6869c56d-86d6-4fe6-b1c3-89c39410cb68&subInst=102000&status=TRADE_SUCCESS
        /*
        {
            "buyerUsername": "136***@163.com",
            "msgType": "trade.notify",
            "payTime": "2023-06-29 14:36:00",
            "buyerCashPayAmt": "1",
            "connectSys": "UNIONPAY",
            "sign": "12C10C5C7ED25B3DFB764987DFE590002D1E77C0426BAB889C22236065122BAA",
            "merName": "合肥市住房租赁发展股份有限公司",
            "mid": "89834017013515M",
            "invoiceAmount": "1",
            "settleDate": "2023-06-29",
            "billFunds": "银行卡:1",
            "buyerId": "2088102119264750",
            "mchntUuid": "2d9081bd88beb8f00188d12467f325d5",
            "tid": "01803523",
            "instMid": "APPDEFAULT",
            "receiptAmount": "1",
            "couponAmount": "0",
            "KA": "Erbn",
            "cardAttr": "CREDIT_CARD",
            "targetOrderId": "2023062922001464751439141095",
            "signType": "SHA256",
            "billFundsDesc": "银行卡支付0.01元。",
            "orderDesc": "合肥市住房租赁发展股份有限公司",
            "seqId": "34913880623N",
            "merOrderId": "33PF202306291432268094563086",
            "targetSys": "Alipay 2.0",
            "totalAmount": "1",
            "createTime": "2023-06-29 14:32:27",
            "buyerPayAmount": "1",
            "notifyId": "6869c56d-86d6-4fe6-b1c3-89c39410cb68",
            "subInst": "102000",
            "status": "TRADE_SUCCESS"
        }
         */
        if (StringUtils.isEmpty(data.getBody())) {
            return null;
        }
        Map<String, String> form = Stream.of(data.getBody().split("&")).map(kv -> {
            String[] p = kv.split("=");
            return new KeyValue<>(p[0], URLDecoder.decode(p[1], StandardCharsets.UTF_8));
        }).collect(Collectors.toMap(KeyValue::getKey, KeyValue::getValue));

        return PayOrderNotifyRespDTO.builder()
                .merchantOrderId(form.get("merOrderId"))
                .channelOrderNo(form.get("targetOrderId"))
                .channelUserId(form.get("buyerId"))
                .payAmount(Long.valueOf(form.get("totalAmount")))
                .successTime(DateUtil.parseLocalDateTime(form.get("payTime")))
                .tradeStatus(form.get("status"))
                .success(true)
                .notifyStatus("SUCCESS")
                .build();
    }

    @Override
    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        ObjectNode json = unifiedRefundParams(reqDTO);
        return call("/v1/netpay/refund", json, (code, resp) -> {
            if (GlobalErrorCodeConstants.SUCCESS.isEqualTo(code)) {
                return new PayRefundUnifiedRespDTO().setMerchantRefundId(json.get("refundOrderId").asText())
                        .setChannelRefundId(resp.get("refundTargetOrderId").asText());
            }
            throw new ServiceException(code);
        });
    }

    protected abstract ObjectNode unifiedRefundParams(PayRefundUnifiedReqDTO reqDTO);

    protected <T> PayCommonResult<T> call(String api, ObjectNode body,
                                          BiFunction<ErrorCode, ObjectNode, T> respFn) {
        Request req;
        try {
            String authorization = getOpenBodySig(config.getAppId(), config.getAppKey(), body.toString());
            req = new Request.Builder()
                    .url(getApiUrl(api))
                    .post(RequestBody.create(body.toString(), MediaType.get("application/json")))
                    .header(HttpHeaders.AUTHORIZATION, authorization)
                    .build();
        } catch (Exception e) {
            return PayCommonResult.error(e);
        }
        log.info("银联下单参数: [{}] {}", api, body);
        try (Response response = okHttpClient.newCall(req).execute()) {
            ObjectNode respBody = JsonUtils.parseObject(response.body().bytes(), ObjectNode.class);
            log.info("银联下单结果: [{}] {}", api, respBody.toString());
            String errCode = JsonUtils.asText(respBody.get("errCode"));
            String errMsg = JsonUtils.asText(respBody.get("errInfo"));
            if (StringUtils.isEmpty(errMsg)) {
                errMsg = JsonUtils.asText(respBody.get("errMsg"));
            }
            return PayCommonResult.build(errCode, errMsg, respFn.apply(codeMapping.apply(errCode, errMsg), respBody), codeMapping);
        } catch (ServiceException e) {
            return PayCommonResult.build(e.getCode(), e.getMessage());
        } catch (Exception e) {
            log.error("[unifiedOrder][{}][request({}) 发起支付失败]", getApiUrl(api), toJsonString(body), e);
            return PayCommonResult.build("ERROR", "下单请求异常", null, codeMapping);
        }
    }

    protected String getApiUrl(String api) {
        return "https://api-mop.chinaums.com" + api;
    }

    protected String getOpenBodySig(String appId, String appKey, String body) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
        String timestamp = DateUtil.format(new Date(), "yyyyMMddHHmmss");
        String nonce = UUID.fastUUID().toString(true);
        byte[] data = body.getBytes(StandardCharsets.UTF_8);
        InputStream is = new ByteArrayInputStream(data);
        String bodyDigest = DigestUtils.sha256Hex(is);
        String str1_C = appId + timestamp + nonce + bodyDigest;

        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(appKey.getBytes(), algorithm));

        byte[] localSignature = mac.doFinal(str1_C.getBytes());
        String localSignatureStr = Base64.encodeBase64String(localSignature);   // Signature
        return ("OPEN-BODY-SIG AppId=" + "\"" + appId + "\"" + ", Timestamp=" + "\"" + timestamp + "\"" + ", Nonce=" + "\"" + nonce + "\"" + ", Signature=" + "\"" + localSignatureStr + "\"");
    }
}
